﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.IO;
using System.Xml.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using GersonFrame.ABFrame;
using GersonFrame.Tool;

namespace GersonFrame
{

    public class BundleEditor
    {
        //========================HotUpdate===========================
        /// <summary>
        /// 热更ABMD5版本配置文件信息 之后的热更信息都是以该文件进行对比
        /// </summary>
        private static string m_VersionMd5Path = Application.dataPath + "/../Hot/ABVersionConfig/" + EditorUserBuildSettings.activeBuildTarget.ToString();
        /// <summary>
        /// 热更AB文件及配置文本信息
        /// </summary>
        private static string m_HotPath = Application.dataPath + "/../Hot/ABFile/" + EditorUserBuildSettings.activeBuildTarget.ToString();
        /// <summary>
        /// 热更配置文件中的ABmd5信息数据
        /// </summary>
        private static Dictionary<string, ABMD5Base> m_PackageMd5Dic = new Dictionary<string, ABMD5Base>();
        /// <summary>
        /// 打包文件生成目录
        /// </summary>
        public static string AssetBundlePath = Application.dataPath + "/../AssetBundle/" + EditorUserBuildSettings.activeBuildTarget.ToString();
        private static string ABCONFIGPATH = ABFrameConfigGeter.Config.AbconfigPath;

        /// <summary>
        /// 所有文件夹ab包dic key是ab包名 value是路径 
        /// </summary>
        private static Dictionary<string, string> m_AllFileDir = new Dictionary<string, string>();
        /// <summary>
        /// 存储文件夹AB包资源路径 用来判断AB包资源重复文件 进行过滤
        /// </summary>
        private static List<string> m_AllFileAB = new List<string>();
        /// <summary>
        /// 单个prefab的独立依赖项  key值就是prefab 名字
        /// </summary>
        private static Dictionary<string, List<string>> m_PrefabAllDependDir = new Dictionary<string, List<string>>();
        /// <summary>
        /// 储存所有有效路径
        /// </summary>
        private static List<string> m_ValueableFilePaths = new List<string>();

        [MenuItem("MyTools/AB/刷新AB配置文件(编辑器环境下)", false, 20)]
        public static void ReflashABEditorConfigFiles()
        {
            SetABsInfo();
            ReflashEditorABConfig();
            ClearABNamesAndProgress();
        }

        [MenuItem("MyTools/AB/BulidAB", false, 20)]
        public static void Build()
        {
            BuildAB();
        }
        public static void BuildAB(bool hotfix = false, string adm5path = null, string hotcount = "1")
        {
            SetABsInfo();
            SetABNames();
            ///刷新AB配置文件
            ReflashABConfigFileInfo();
            //  构建AssetBundle
            BuildAssetBundle();
            //热更包
            if (hotfix)
                ReadMD5Info(adm5path, hotcount);
            ////非热更包
            else
                WriteABMD5();

            ClearABNamesAndProgress();
        }

        [MenuItem("MyTools/AB/打开AB资源文件夹", false, 30)]
        public static void OpenABFileFloder()
        {
            Application.OpenURL("file://" + AssetBundlePath);
        }

        [MenuItem("MyTools/热更/打开热更AB资源文件夹", false, 30)]
        public static void OpenHotABFileFloder()
        {
            Application.OpenURL("file://" + m_HotPath);
        }

        [MenuItem("MyTools/热更/打开更ABMD5配置信息文件夹", false, 30)]
        public static void OpenHotABMd5InfoFileFloder()
        {
            Application.OpenURL("file://" + m_VersionMd5Path);
        }

        /// <summary>
        /// 设置AB包信息
        /// </summary>
        static void SetABsInfo()
        {
            m_ValueableFilePaths.Clear();
            m_PrefabAllDependDir.Clear();
            m_AllFileAB.Clear();
            m_AllFileDir.Clear();
            ABConfig abconfig = AssetDatabase.LoadAssetAtPath<ABConfig>(ABCONFIGPATH);
            //文件夹打包
            foreach (ABConfig.FileDirABName filedir in abconfig.m_AllFileDirAB)
            {
                if (m_AllFileDir.ContainsKey(filedir.ABName))
                    MyDebuger.LogError(string.Format("ab包 {0} 名字配置重复", filedir.ABName));
                else
                {
                    m_AllFileDir.Add(filedir.ABName, filedir.Path);
                    m_AllFileAB.Add(filedir.Path);
                    m_ValueableFilePaths.Add(filedir.Path);
                }
            }
            int prefabcount = 0;
            string[] allstr = null;
            if (abconfig.m_PrefabsPath.Count > 0)
            {
                allstr = AssetDatabase.FindAssets("t:Prefab", abconfig.m_PrefabsPath.ToArray());
                prefabcount = allstr.Length;
            }
            //所有 prefab 资源路径guid
            for (int i = 0; i < prefabcount; i++)
            {
                //guid 转路径
                string path = AssetDatabase.GUIDToAssetPath(allstr[i]);
                EditorUtility.DisplayProgressBar("查找prefab ", path, i * 1.0f / allstr.Length);
                m_ValueableFilePaths.Add(path);
                //找到prefab所有依赖项
                if (!IsContainAllFileAB(path))
                {
                    GameObject obj = AssetDatabase.LoadAssetAtPath<GameObject>(path);
                    //获取所有依赖项  会包含脚本
                    string[] alldepend = AssetDatabase.GetDependencies(path);
                    List<string> alldependPathList = new List<string>();
                    for (int j = 0; j < alldepend.Length; j++)
                    {
                        //判断依赖项是否在文件夹ab包资源站中且不是脚本
                        if (!IsContainAllFileAB(alldepend[j]) && !alldepend[j].EndsWith(".cs"))
                        {
                            //添加到过滤列表
                            m_AllFileAB.Add(alldepend[j]);
                            //加入当前预制体的依赖队列
                            alldependPathList.Add(alldepend[j]);
                        }
                    }
                    if (m_PrefabAllDependDir.ContainsKey(obj.name))
                        MyDebuger.LogError("存在相同名字的 prefab " + obj.name);
                    else
                    {
                        m_PrefabAllDependDir.Add(obj.name, alldependPathList);
                    }
                }
            }
        }

        /// <summary>
        /// 构建assetbundle文件
        /// </summary>
        static void BuildAssetBundle()
        {
            AssetBundleManifest manifest = BuildPipeline.BuildAssetBundles(AssetBundlePath, BuildAssetBundleOptions.ChunkBasedCompression, EditorUserBuildSettings.activeBuildTarget);
            if (manifest == null)
                MyDebuger.LogError("Asset Bundle 打包失败");
            else
                MyDebuger.Log("Asset Bundle 打包完毕");
            EncryptAB();
        }
         

        [MenuItem("Tools/加密AB包")]
        public static void EncryptAB()
        {
            DirectoryInfo directory = new DirectoryInfo(AssetBundlePath);
            FileInfo[] files = directory.GetFiles("*", SearchOption.AllDirectories);
            if (files.Length < 1)
            {
                Debug.LogWarning("没有可以加密的文件");
                return;
            }
            for (int i = 0; i < files.Length; i++)
            {
                if (files[i].Name.EndsWith(".meta") || files[i].Name.EndsWith(".manifest"))
                {
                    continue;
                }
                AES.AESFileEncrypt(files[i].FullName, "Gerson");
            }
            DeleteMainfest();
            Debug.Log("AB包加密完毕");
        }


        [MenuItem("Tools/解密AB包")]
        public static void DecryptAB()
        {
            DirectoryInfo directory = new DirectoryInfo(AssetBundlePath);
            FileInfo[] files = directory.GetFiles("*", SearchOption.AllDirectories);
            for (int i = 0; i < files.Length; i++)
            {
                if (files[i].Name.EndsWith(".meta") || files[i].Name.EndsWith(".manifest"))
                {
                    continue;
                }
                AES.AESFileDecrypt(files[i].FullName, "Gerson");
            }
            Debug.Log("AB包解密完毕");
        }



    

        /// <summary>
        /// 删除manifest 文件
        /// </summary>
        static void DeleteMainfest()
        {
            DirectoryInfo directory = new DirectoryInfo(AssetBundlePath);
            FileInfo[] files = directory.GetFiles("*", SearchOption.AllDirectories);
            for (int i = 0; i < files.Length; i++)
            {
                if (files[i].Name.EndsWith(".manifest"))
                {
                    File.Delete(files[i].FullName);
                }
            }
        }

        //============================刷新AB配置文件=========================
        /// <summary>
        /// 刷新编辑器下的AB配置信息
        /// </summary>
        static void ReflashEditorABConfig()
        {
            Dictionary<string, string> respathDic = new Dictionary<string, string>();
            foreach (var name in m_AllFileDir.Keys)
            {
                string path = m_AllFileDir[name];
                SetEditorABPath(path, respathDic, name);
            }
            //设置prefab ab 包名
            foreach (var name in m_PrefabAllDependDir.Keys)
            {
                List<string> dependences = m_PrefabAllDependDir[name];
                for (int i = 0; i < dependences.Count; i++)
                {
                    SetEditorABPath(dependences[i], respathDic, name);
                }
            }

            if (!Directory.Exists(AssetBundlePath))
                Directory.CreateDirectory(AssetBundlePath);

            WriteABConfig(respathDic);
        }

        static void SetEditorABPath(string path, Dictionary<string, string> pathdic, string abName)
        {
            if (!Directory.Exists(path))
            {
                pathdic.Add(path, abName);
            }
            else
            {
                string[] fileList = Directory.GetFileSystemEntries(path);
                foreach (string file in fileList)
                {
                    if (Directory.Exists(file))
                        SetEditorABPath(file, pathdic, abName);
                    else
                    {
                        StringBuilder stringBuilder = new StringBuilder();
                        stringBuilder.Append(path);
                        string filename = Path.GetFileName(file);
                        if (filename.EndsWith(".cs") || filename.EndsWith(".meta"))
                            continue;
                        stringBuilder.Append("/");
                        stringBuilder.Append(filename);
                        stringBuilder.Replace("\\", "/");
                        pathdic.Add(stringBuilder.ToString(), abName);
                    }
                }
            }
        }

        /// <summary>
        /// 刷新AB配置信息
        /// </summary>
        static void ReflashABConfigFileInfo()
        {
            string[] allBundleNames = AssetDatabase.GetAllAssetBundleNames();
            //key为全路径 value 为包名 用来生成XML配置
            Dictionary<string, string> respathDic = new Dictionary<string, string>();
            for (int i = 0; i < allBundleNames.Length; i++)
            {
                List<string> assetnamelist = new List<string>();
                string abpath = "";
                m_AllFileDir.TryGetValue(allBundleNames[i],out abpath);
                string[] allBundlePath = AssetDatabase.GetAssetPathsFromAssetBundle(allBundleNames[i]);
                for (int j = 0; j < allBundlePath.Length; j++)
                {
                    if (allBundlePath[j].EndsWith(".cs"))
                        continue;
                    string assetname= allBundlePath[j].Remove(0, allBundlePath[j].LastIndexOf("/") + 1);
                    if (!string.IsNullOrEmpty(abpath)&& allBundlePath[j].Contains(abpath)&& assetnamelist.Contains(assetname))
                    {
                        MyDebuger.LogError(string.Format("此AB包:{0} 资源名重复: {1} 资源路径:{2}", allBundleNames[i],assetname, allBundlePath[j]));
                    }
                    else
                    {
                        assetnamelist.Add(assetname);
                        respathDic.Add(allBundlePath[j], allBundleNames[i]);
                    }
                }
            }

            if (!Directory.Exists(AssetBundlePath))
                Directory.CreateDirectory(AssetBundlePath);

            // 删除没有用的AB包 为了减少打包时间 有时候不是所有资源都要重新打包 要重新打包的资源手动删除或者修改配置文件
            DeleteUnUseAsseBundle();
            //生成 AB包配置表
            WriteABConfig(respathDic);
        }

        //============================================================

        /// <summary>
        /// 将配置信息写入AB配置文件
        /// </summary>
        static void WriteABConfig(Dictionary<string, string> respathDic)
        {
            AssetBundleConfig assetBundleConfig = new AssetBundleConfig();
            assetBundleConfig.ABList = new List<ABBase>();
            foreach (string path in respathDic.Keys)
            {
                if (!ValidPath(path)) continue;
                ABBase aBBase = new ABBase();
                aBBase.Path = path;
                aBBase.Crc = Crc32.GetCrc32(path);
                aBBase.ABName = respathDic[path];
                aBBase.AssetName = path.Remove(0, path.LastIndexOf("/") + 1);
                aBBase.ABDependences = new List<string>();
                //获取所有依赖项
                string[] resdependence = AssetDatabase.GetDependencies(path);
                for (int i = 0; i < resdependence.Length; i++)
                {
                    string tempPath = resdependence[i];
                    //排除自己和 cs脚本
                    if (tempPath == path || path.EndsWith(".cs")) continue;
                    string abName = "";
                    //依赖项处理
                    if (respathDic.TryGetValue(tempPath, out abName))
                    {
                        //如果依赖包已经存在
                        if (abName == respathDic[path]) continue;

                        if (!aBBase.ABDependences.Contains(abName))
                            aBBase.ABDependences.Add(abName);
                    }
                }
                assetBundleConfig.ABList.Add(aBBase);
            }


            //写入xml
            string xmlpath = ABFrameConfigGeter.Config.AssetbundleXMLConfigPath;
            if (File.Exists(xmlpath)) File.Delete(xmlpath);
            FileStream fs = new FileStream(xmlpath, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite);
            StreamWriter sw = new StreamWriter(fs, Encoding.UTF8);
            XmlSerializer xs = new XmlSerializer(assetBundleConfig.GetType());
            xs.Serialize(sw, assetBundleConfig);
            sw.Close();
            fs.Close();

            //写入二进制
            //优化掉path 路径 path 是用来观察的
            //foreach (ABBase abbase in assetBundleConfig.ABList)
            //    abbase.Path = "";
            string bytepath = ABFrameConfigGeter.Config.AssetbundleBytesConfigPath;
            FileStream fbs = new FileStream(bytepath, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite);
            //清空文件流
            fbs.Seek(0, SeekOrigin.Begin);
            fbs.SetLength(0);
            BinaryFormatter bfm = new BinaryFormatter();
            bfm.Serialize(fbs, assetBundleConfig);
            fbs.Close();
            //刷新才会写入AB 包
            AssetDatabase.Refresh();
            int index = bytepath.IndexOf("Assets");
            string newbytepath = bytepath.Remove(0, index);
            SetABName("assetbundleconfig", newbytepath);
        }

        /// <summary>
        /// 删除没有用的AB包 为了减少打包时间 有时候不是所有资源都要重新打包 要重新打包的资源手动删除或者修改配置文件
        /// </summary>
        static void DeleteUnUseAsseBundle()
        {
            string[] allBundlesName = AssetDatabase.GetAllAssetBundleNames();
            DirectoryInfo directoryInfo = new DirectoryInfo(AssetBundlePath);
            //获取bunlde目录下的所有文件
            FileInfo[] files = directoryInfo.GetFiles("*", SearchOption.AllDirectories);
            for (int i = 0; i < files.Length; i++)
            {
                if (ContainABName(files[i].Name, allBundlesName) || files[i].Name.EndsWith(".meta") || files[i].Name.EndsWith(".manifest") || files[i].Name.EndsWith("assetbundleconfig"))
                    continue;
                else
                {
                    MyDebuger.Log("删除无效或过时资源: " + files[i].Name);
                    if (File.Exists(files[i].FullName))
                        File.Delete(files[i].FullName);
                }
            }
        }

        /// <summary>
        /// 清除AB 包名和进度条
        /// </summary>
        public static void ClearABNamesAndProgress()
        {
            //获取所有的AB包名
            string[] oldABNames = AssetDatabase.GetAllAssetBundleNames();
            //清除之前的AB包名 因为会在meta文件中生成对应的依赖关系 
            for (int i = 0; i < oldABNames.Length; i++)
            {
                AssetDatabase.RemoveAssetBundleName(oldABNames[i], true);
                EditorUtility.DisplayProgressBar("清除AB包名:", oldABNames[i], i * 1.0f / oldABNames.Length);
            }
            AssetDatabase.SaveAssets();
            AssetDatabase.Refresh();
            EditorUtility.ClearProgressBar();
        }


        /// <summary>
        ///当前存在的bundle 是否存在配置表中配置的包名
        /// </summary>
        /// <returns></returns>
        static bool ContainABName(string abname, string[] allAbNames)
        {
            for (int i = 0; i < allAbNames.Length; i++)
            {
                if (abname == allAbNames[i]) return true;
            }
            return false;
        }

        //===============================设置AB包名===============================
        /// <summary>
        /// 设置配置好的AB名字
        /// </summary>
        private static void SetABNames()
        {
            //设置文件夹ab包名
            foreach (var name in m_AllFileDir.Keys)
            {
                SetABName(name, m_AllFileDir[name]);
            }
            //设置prefab ab 包名
            foreach (var name in m_PrefabAllDependDir.Keys)
            {
                SetABName(name, m_PrefabAllDependDir[name]);
            }
        }


        /// <summary>
        /// 通过代码设置AB包的名字 
        /// </summary>
        static void SetABName(string abName, string path)
        {
            AssetImporter assetImporter = AssetImporter.GetAtPath(path);
            if (assetImporter == null) MyDebuger.LogError("不存在此路径文件" + path);
            else
                assetImporter.assetBundleName = abName;
        }

        /// <summary>
        /// 通过代码设置AB包的名字 
        /// </summary>
        static void SetABName(string abName, List<string> pathList)
        {
            for (int i = 0; i < pathList.Count; i++)
            {
                SetABName(abName, pathList[i]);
            }
        }


        //=============================================================

        /// <summary>
        /// Ab资源是否已经包含
        /// </summary>
        /// <returns></returns>
        static bool IsContainAllFileAB(string abPath)
        {
            for (int i = 0; i < m_AllFileAB.Count; i++)
            {
                //test/      testaa/a.prefab
                if (m_AllFileAB[i] == abPath || (abPath.Contains(m_AllFileAB[i]) && (abPath.Replace(m_AllFileAB[i], "")[0] == '/')))
                    return true;
            }
            return false;
        }

        

        /// <summary>
        /// 是否是有效路径
        /// </summary>
        /// <returns></returns>
        static bool ValidPath(string path)
        {
            for (int i = 0; i < m_ValueableFilePaths.Count; i++)
            {
                if (path.Contains(m_ValueableFilePaths[i]))
                    return true;
            }
            return false;
        }

        /// <summary>
        /// 同一個包內资源名是否重复
        /// </summary>
        /// <returns></returns>
        static bool IsSameAssetsName(string abname,string assetname)
        {

            return false;
        }


        //=============================HotUpdate===================

        /// <summary>
        /// 写入Md5文件
        /// </summary>
        static void WriteABMD5()
        {
            DirectoryInfo directoryInfo = new DirectoryInfo(AssetBundlePath);
            FileInfo[] files = directoryInfo.GetFiles("*", SearchOption.AllDirectories);
            //ABMD5管理信息类
            ABMD5 abMd5 = new ABMD5();
            abMd5.ABMD5List = new List<ABMD5Base>();
            for (int i = 0; i < files.Length; i++)
            {
                if (files[i].Name.EndsWith(".meta") || files[i].Name.EndsWith(".manifest"))
                    continue;
                ABMD5Base aBMD5Base = new ABMD5Base();
                aBMD5Base.Name = files[i].Name;
                aBMD5Base.Md5 = MD5Manager.Instance.BuildFileMd5(files[i].FullName);
                aBMD5Base.Size = files[i].Length / 1024.0f;//kb
                abMd5.ABMD5List.Add(aBMD5Base);
            }
            ///本地的md5配置文件信息
            string ABMD5Path = Application.dataPath + "/Resources/ABMD5.bytes";
            BinarySerializeOpt.BinarySerilize(ABMD5Path, abMd5);

            //=========在外部保存MD5资源版本信息=================
            if (!Directory.Exists(m_VersionMd5Path))
                Directory.CreateDirectory(m_VersionMd5Path);
            //存储不同版本 不同平台 md5文件信息
            string targetPath = m_VersionMd5Path + "/ABMD5_" + PlayerSettings.bundleVersion + ".bytes";
            if (File.Exists(targetPath))
                File.Delete(targetPath);
            File.Copy(ABMD5Path, targetPath);
        }


        /// <summary>
        /// 读取热更ABMd5配置文件信息 进行对比分析
        /// </summary>
        /// <param name="abMd5Path"></param>
        /// <param name="hotcount"></param>
        static void ReadMD5Info(string abMd5Path, string hotcount)
        {
            m_PackageMd5Dic.Clear();
            using (FileStream fileStream = new FileStream(abMd5Path, FileMode.Open, FileAccess.Read))
            {
                BinaryFormatter bf = new BinaryFormatter();
                ABMD5 aBMD5 = bf.Deserialize(fileStream) as ABMD5;
                foreach (ABMD5Base adBase in aBMD5.ABMD5List)
                {
                    m_PackageMd5Dic.Add(adBase.Name, adBase);
                }
            }
            //========判断ab资源哪些改变了================
            List<string> changeList = new List<string>();
            DirectoryInfo directory = new DirectoryInfo(AssetBundlePath);
            FileInfo[] files = directory.GetFiles("*");
            for (int i = 0; i < files.Length; i++)
            {
                if (files[i].Name.EndsWith(".meta") || files[i].Name.EndsWith(".manifest"))
                    continue;
                string name = files[i].Name;
                string md5 = MD5Manager.Instance.BuildFileMd5(files[i].FullName);
                ABMD5Base aBMD5Base = null;
                //原热更配置文件不包含该文件 直接添加
                if (!m_PackageMd5Dic.ContainsKey(name))
                    changeList.Add(name);
                else
                {
                    if (m_PackageMd5Dic.TryGetValue(name, out aBMD5Base))
                    {
                        //对比md5信息 原热更配置文件和当前文件不一样 添加
                        if (md5 != aBMD5Base.Md5)
                            changeList.Add(name);
                    }
                }
            }
            CopyABAndGeneratXml(changeList, hotcount);
        }


        /// <summary>
        /// 复制需要热更的ab包和构建xml配置表文本信息
        /// </summary>
        static void CopyABAndGeneratXml(List<string> changeList, string hotcount)
        {
            if (!Directory.Exists(m_HotPath))
            {
                Directory.CreateDirectory(m_HotPath);
            }
            DeleAllFile(m_HotPath);

            //======拷贝要热更的文件到指定目录======
            foreach (string str in changeList)
            {
                if (!str.EndsWith(".manifes"))
                {
                    File.Copy(AssetBundlePath + "/" + str, m_HotPath + "/" + str);
                }
            }
            //=============生成服务器Patch===================
            DirectoryInfo directory = new DirectoryInfo(m_HotPath);
            FileInfo[] files = directory.GetFiles("*", SearchOption.AllDirectories);
            Pathces patches = new Pathces();
            patches.Version = hotcount;
            patches.Files = new List<Patch>();
            for (int i = 0; i < files.Length; i++)
            {
                Patch patch = new Patch();
                patch.MD5 = MD5Manager.Instance.BuildFileMd5(files[i].FullName);
                patch.Name = files[i].Name;
                patch.Size = files[i].Length / 1024.0f;
                patch.Platform = EditorUserBuildSettings.activeBuildTarget.ToString();
                ///下载路径
                patch.Url = HotPatchManager.ServerUrl + "AssetBundle/" + PlayerSettings.bundleVersion + "/" + hotcount + "/" + files[i].Name;
                patches.Files.Add(patch);
            }
            BinarySerializeOpt.Xmlserialize(m_HotPath + "/Patch.xml", patches);
        }

        /// <summary>
        /// 删除指定目录下的所有文件
        /// </summary>
        /// <param name="fullPath"></param>
        /// <returns></returns>
        public static bool DeleAllFile(string fullPath)
        {
            if (Directory.Exists(fullPath))
            {
                DirectoryInfo directoryInfo = new DirectoryInfo(fullPath);
                FileInfo[] files = directoryInfo.GetFiles("*", SearchOption.AllDirectories);
                for (int i = 0; i < files.Length; i++)
                {
                    if (files[i].Name.EndsWith(".meta")) continue;
                    File.Delete(files[i].FullName);
                }
                return true;
            }
            return false;
        }


    }

}
